package com.ssttevee.taskmanagerapp.tasks;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.lang.Math;

public class ServerQuery {
	
	private String motd;
	private int maxPlayers;
	private int playerCount;
	private String players;
	private int latency;
	private String version;
	private int times_online;
	private int times_checked;
	private boolean online;
	private static InetAddress ipAddr;
	private static DatagramSocket dp;
	private static DatagramPacket dpr;
	public final static String STATISTIC = "\u0000";
	public final static String HANDSHAKE = "\u0009";
	
	public static final String allowedCharacters = getAllowedCharacters();

	public String getMotd() {
		return motd;
	}

	public void setMotd(String motd) {
		this.motd = motd;
	}

	public int getMaxPlayers() {
		return maxPlayers;
	}

	public void setMaxPlayers(int maxPlayers) {
		this.maxPlayers = maxPlayers;
	}

	public int getPlayerCount() {
		return playerCount;
	}

	public void setPlayerCount(int playerCount) {
		this.playerCount = playerCount;
	}

	public String getPlayers() {
		return players;
	}

	public void setPlayers(String players) {
		this.players = players;
	}

	public int getLatency() {
		return latency;
	}

	public void setLatency(int latency) {
		this.latency = latency;
	}

	public String getVersion() {
		return version;
	}

	public void setVersion(String version) {
		this.version = version;
	}

	public int getTimesOnline() {
		return times_online;
	}

	public void setTimesOnline(int times_online) {
		this.times_online = times_online;
	}

	public void addOnlineTally() {
		this.times_online = this.times_online + 1;
	}

	public int getTimesChecked() {
		return times_checked;
	}

	public void setTimesChecked(int times_checked) {
		this.times_checked = times_checked;
	}

	public void addCheckedTally() {
		this.times_checked = this.times_checked + 1;
	}

	public boolean isOnline() {
		return online;
	}

	public void setOnline(boolean online) {
		this.online = online;
	}
	
	public void queryServer(Task t) throws IOException {
        InetAddress ia = parseIp(t.getHost().split(":")[0]);
        DatagramSocket dsg = initConnection(ia, t.getGs4Port());
		
		queryServer(t, ia, dsg);
	}

    public void queryServer(Task t, InetAddress addr, DatagramSocket dgs) throws IOException {
        
        try {
            ipAddr = addr;
            dp = dgs;
            
            String command = "xx\u0009xxxx";
            byte[] sender = command.getBytes();
            sender[0] = (byte)0xFE;
            sender[1] = (byte)0xFD;
            sender[command.length() - 1] = (byte)0x04;
            sender[command.length() - 2] = (byte)0x03;
            sender[command.length() - 3] = (byte)0x02;
            sender[command.length() - 4] = (byte)0x01;

            dpr = writeData(sender, ipAddr, t.getGs4Port(), 512);
            
            String s3 = new String(dpr.getData(),0,dpr.getLength());
            s3 = s3.substring(5).replaceAll( "[^\\d]", "" );

            command = "xx\u0000xxxxyyyyxxxx";

            sender = command.getBytes();
            byte[] challenge = packIt(Integer.parseInt(s3));
            
            sender[0] = (byte)254;
            sender[1] = (byte)253;
            sender[3] = (byte)0x01;
            sender[4] = (byte)0x02;
            sender[5] = (byte)0x03;
            sender[6] = (byte)0x04;
            sender[7] = challenge[0];
            sender[8] = challenge[1];
            sender[9] = challenge[2];
            sender[10] = challenge[3];
            sender[11] = (byte)0x00;
            sender[12] = (byte)0x00;
            sender[13] = (byte)0x00;
            sender[14] = (byte)0x00;

            dpr = writeData(sender, ipAddr, t.getGs4Port(), 4096);
            
            String s4 = new String(dpr.getData(),0,dpr.getLength());
            
            s4 = s4.substring(5).substring(11);
            String[] s5 = s4.split("\u0000\u0000\u0001player_\u0000\u0000");

            String[] players = s5[1].substring(0, s5[1].length() - 1).split("\u0000");
            String playerslist = "";
            for(String p : players) {
            	if(p == players[players.length - 1]) {
            		playerslist = playerslist + p;
            	} else {
        			playerslist = playerslist + p + ", ";
            	}
            }
            
            setPlayers(playerslist);

            String[] data = s5[0].split("\u0000");
            String[] xdata = null;
            String last = "";
            int b = 0;
            
            for(String d : data) {
    			if( (b & 1) != 0 ) {
    				if(last.length() > 0) {
        				last = last + "\u0000\u0000";
    				}
    				last = last + d;
    			}
    			b++;
            }
            
            xdata = last.split("\u0000\u0000");
            
            
            setMotd(xdata[0]);
            setVersion(xdata[3]);
            setPlayerCount(Integer.parseInt(xdata[6]));
            setMaxPlayers(Integer.parseInt(xdata[7]));
            
            
            

            
        } finally {
            dp.close();
        }
    }

    public void pollServer(Task t) throws IOException {
        String s = t.getHost();
        String as[] = s.split(":");

        if (s.startsWith("[")) {
            int i = s.indexOf("]");

            if (i > 0) {
                String s2 = s.substring(1, i);
                String s3 = s.substring(i + 1).trim();

                if (s3.startsWith(":") && s3.length() > 0) {
                    s3 = s3.substring(1);
                    as = new String[2];
                    as[0] = s2;
                    as[1] = s3;
                } else {
                    as = new String[1];
                    as[0] = s2;
                }
            }
        }

        if (as.length > 2) {
            as = new String[1];
            as[0] = s;
        }
        
        String s1 = as[0];
        int j = as.length <= 1 ? 25565 : parseIntWithDefault(as[1], 25565);
        Socket socket = null;
        DataInputStream datainputstream = null;
        DataOutputStream dataoutputstream = null;
        
        try {
            socket = new Socket();
            socket.setSoTimeout(3000);
            socket.setTcpNoDelay(true);
            socket.setTrafficClass(18);
            
            long startTime = System.nanoTime();
            socket.connect(new InetSocketAddress(s1, j), 3000);
            float estimatedTime = (System.nanoTime() - startTime) / 1000000;
            setLatency((int) Math.floor(estimatedTime + 0.5f));
            
            datainputstream = new DataInputStream(socket.getInputStream());
            dataoutputstream = new DataOutputStream(socket.getOutputStream());
            dataoutputstream.write(254);

            if (datainputstream.read() != 255) {
                throw new IOException("Bad message");
            }

            String s4 = readString(datainputstream, 256);
            char ac[] = s4.toCharArray();

            for (int k = 0; k < ac.length; k++) {
                if (ac[k] != '\247' && allowedCharacters.indexOf(ac[k]) < 0) {
                    ac[k] = '?';
                }
            }

            s4 = new String(ac);
            String as1[] = s4.split("\247");
            s4 = as1[0];
            int l = -1;
            int i1 = -1;

            try {
                l = Integer.parseInt(as1[1]);
                i1 = Integer.parseInt(as1[2]);
            }
            catch (Exception exception) { }

            setMotd((new StringBuilder()).append(s4).toString());

            addCheckedTally();
            
            if (l >= 0 && i1 > 0) {
                setPlayerCount(l);
                setMaxPlayers(i1);
                addOnlineTally();
                setOnline(true);
            } else {
                setPlayerCount(0);
                setMaxPlayers(0);
                setMotd("Can't reach server");
            }
        } catch(UnknownHostException e) {
            setMotd("Can't resolve hostname");
            setOnline(false);
            setLatency(-1);
            setMaxPlayers(0);
            setPlayerCount(0);
        } finally {
            try {
                if (datainputstream != null) {
                    datainputstream.close();
                }
            } catch (Throwable throwable) { }

            try {
                if (dataoutputstream != null) {
                    dataoutputstream.close();
                }
            } catch (Throwable throwable1) { }

            try {
                if (socket != null) {
                    socket.close();
                }
            } catch (Throwable throwable2) {
                setMotd("Can't reach server");
            }
        }
    }

    private int parseIntWithDefault(String par1Str, int par2) {
        try {
            return Integer.parseInt(par1Str.trim());
        } catch (Exception exception) {
            return par2;
        }
    }
    
    public static String readString(DataInputStream par0DataInputStream, int par1) throws IOException {
        short word0 = par0DataInputStream.readShort();

        if (word0 > par1) {
            throw new IOException((new StringBuilder()).append("Received string length longer than maximum allowed (").append(word0).append(" > ").append(par1).append(")").toString());
        }

        if (word0 < 0) {
            throw new IOException("Received string length is less than zero! Weird string!");
        }

        StringBuilder stringbuilder = new StringBuilder();

        for (int i = 0; i < word0; i++) {
            stringbuilder.append(par0DataInputStream.readChar());
        }

        return stringbuilder.toString();
    }

    private static String getAllowedCharacters() {
        String s = " !#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_'abcdefghijklmnopqrstuvwxyz{|}~⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜø£Ø×ƒáíóúñÑªº¿®¬½¼¡«»";

        s = (new StringBuilder()).append(s).append('"').toString();
        s = (new StringBuilder()).append(s).append('\\').toString();

        return s;
        
    }
    
	public boolean validateAddress(String addr) {
		try {
			InetAddress.getByName(addr);
			return true;
		} catch (UnknownHostException e) {
			return false;
		}
	}
    
    private static byte[] packIt(int j) {
        int x = (int) Math.floor(j/65536);
        int y = (int) Math.floor((j-(x*65536))/256);
        int z = (int) Math.floor(j-((x*65536)+(y*256)));
        
        String str = "wxyz";
        
        byte[] f = str.getBytes();
        
        f[0] = (byte) 0;
        f[1] = (byte) x;
        f[2] = (byte) y;
        f[3] = (byte) z;
        
		return f;
    }
    
	public InetAddress parseIp(String addr) throws UnknownHostException {
		return InetAddress.getByName(addr);
	}
	
	public DatagramSocket initConnection(InetAddress addr, int port) throws SocketException {
    	dp = new DatagramSocket();
        dp.connect(addr, port);
        dp.setSoTimeout(3000);
		return dp;
	}
    
	public DatagramPacket writeData(byte[] sender, InetAddress ipAddress, int port, int buflen) throws IOException {

        DatagramPacket dps = new DatagramPacket(sender, sender.length, ipAddress, port);
		dp.send(dps);
        
        byte[] buffer = new byte[buflen];
        dps = new DatagramPacket(buffer,buffer.length);
		dp.receive(dps);
        
        return dps;
	}
}
